﻿HmJS.$Import('core.element');

/*
---
version: 0.2-0
description: Determines when the user is idle (not interacting with the page) so that you can respond appropriately.
license: MIT-style license
download: http://mootools.net/forge/p/idletimer
source: http://github.com/rexxars/IdleTimer

authors:
- Espen 'Rexxars' Hovlandsdal (http://rexxars.com/)

requires:
core/1.2.4:
- Class.Extras
- Element.Event

provides:
- IdleTimer

inspiration:
- Inspired by Nicholas C. Zakas' Idle Timer (http://yuilibrary.com/gallery/show/idletimer) Copyright (c) 2009 Nicholas C. Zakas, [YUI BSD](http://developer.yahoo.com/yui/license.html)
- Also inspired by Paul Irish's jQuery idleTimer (http://paulirish.com/2009/jquery-idletimer-plugin/) Copyright (c) 2009 Paul Irish, [MIT License](http://opensource.org/licenses/mit-license.php)
...
*/

/*
How to use
----------

An easy setup of IdleTimer might be something like this:

	#JS
	var myIdleTimer = new IdleTimer(window, { timeout: 60000 });
	myIdleTimer.addEvent('idle', function() {
		alert('You have been idle for 60 seconds!');
	});

This would launch an alert box after the user has not moved his mouse button over the document, clicked any mouse/keyboard buttons or scrolled using the mouse scroll wheel.

If you want to use the default settings (60 second timeout), you can also do it even easier by doing something like this:

	#JS
	window.addEvents({
		'idle': function() { alert('60 seconds have passed!'); },
		'active': function() { alert('You just became active again!'); }
	});

If you want to apply the IdleTimer to a specific element on the page, you can get an IdleTimer instance and set it's options like this:

	#JS
	$('myDiv').get('idle'); // Returns an IdleTimer instance
	$('myDiv').set('idle', { timeout: 5000, events: ['mouseover', 'mousedown'] }); // Creates an IdleTimer instance with the given options (does not start it)

Syntax
------

	#JS
	new IdleTimer(element, [options]);

Arguments
---------

	1. element - (mixed) An Element or an id string.
	2. options - (object, optional) the options described below:

Options
-------

* timeout : (number) Number of idle milliseconds before firing the idle event. Defaults to 60000 (one minute).
* events  : (array) Event names that trigger the active event (resets the idle time). Defaults to ['mousemove', 'keydown', 'mousewheel', 'mousedown']

Events
------

* idle           : Fired when the user has reached the set amount of idle milliseconds.
* active         : Fired when the user becomes active.
* start          : Fired when the events have been added and the timer is starting for the first time.
* stop           : Fired when the timer has been stopped and the events have been removed.
* timeoutChanged : Fired when the threshold for idle milliseconds has been set to a new value. Gets passed the new value.
*/

HmJS.register('util.IdleTimer', function ($ns) {

	var IdleTimer = new Class({

		Implements: [Events, Options],

		options: {
			/*
			onStart: function(){},
			onStop: function(){},
			onIdle: function(){},
			onActive: function(){},
			onTimeoutChanged: function(){},
			*/
			timeout: 60000,
			events: ['mousemove', 'keydown', 'mousewheel', 'mousedown']
		},

		initialize: function (element, options) {
			this.setOptions(options);
			this.element = document.id(element);
			this.activeBound = this.active.bind(this);
			this.isIdle = false;
			this.started = false;
		},

		/**
		* Stops any existing timeouts and removes the bound events
		*/
		stop: function () {
			clearTimeout(this.timer);

			// Remove bound events
			for (var i = 0; i < this.options.events.length; i++) {
				this.element.removeEvent(this.options.events[i], this.activeBound);
			}
			this.bound = false;
			this.started = false;
			this.fireEvent('stop');
			return this;
		},

		/**
		* Triggered when the user becomes active. May also be launched manually by scripts
		* if implementing some sort of custom events etc. An example would be flash files
		* which does not trigger the documents onmousemove event, you could have the flash
		* call this method to prevent the idle event from being triggered.
		*/
		active: function () {
			clearTimeout(this.timer);
			if (this.isIdle) { this.fireEvent('active'); }
			this.isIdle = false;
			this.start();
		},

		/**
		* Fired when the user becomes idle
		*/
		idle: function () {
			if (this.timer) { clearTimeout(this.timer); } // If called manually, timer will have to be removed
			this.isIdle = true;
			this.fireEvent('idle');
		},

		/**
		* Starts the timer which eventually will reach idle() if the user is inactive
		*/
		start: function () {
			this.timer = this.idle.delay(this.options.timeout, this);
			this.lastActive = Date.now();
			if (!this.bound) { this.bind(); }
			this.started = true;
			return this;
		},

		/**
		* Bind events to the element
		*/
		bind: function () {
			for (var i = 0, len = this.options.events.length; i < len; i++) {
				this.element.addEvent(this.options.events[i], this.activeBound);
			}
			this.bound = true;
			this.fireEvent('start');
		},

		/**
		* Returns how many seconds/milliseconds have passed since the user was last idle
		*/
		getIdleTime: function (returnSeconds) {
			return returnSeconds ? Math.round((Date.now() - this.lastActive) / 1000) : Date.now() - this.lastActive;
		},

		/**
		* Sets the number of milliseconds is concidered "idle".
		* Will also attempt to fix any difference in the old and new timeout values,
		* unless you pass true as whenActive - in this case the new timeout will be
		* in play the next time the user is active again.
		*/
		setTimeout: function (newTime, whenActive) {
			var old = this.options.timeout;
			this.options.timeout = newTime;

			if (whenActive) { return this; } // The developer wants to wait until the next time the user is active before setting the new timeout

			// In all cases, we need a new timer
			clearTimeout(this.timer);

			// Fire a new timeout event
			this.fireEvent('timeoutChanged', newTime);

			// How much time has ellapsed since we were last active?
			var elapsed = this.getIdleTime();

			if (elapsed < newTime && this.isIdle) {
				// Set as active, cause the new "idle" time has not been reached
				this.fireEvent('active');
				this.isIdle = false;
			} else if (elapsed >= newTime) {
				// We've not reached the limit before, but with the new timeout, we now have.
				this.idle();
				return this;
			}

			// Set new timer
			this.timer = this.idle.delay(newTime - elapsed, this);
			return this;
		}

	});

	Element.Properties.idle = {

		set: function (options) {
			var idle = this.retrieve('idle');
			if (idle) { idle.stop(); }
			return this.eliminate('idle').store('idle:options', options);
		},

		get: function (options) {
			if (options || !this.retrieve('idle')) {
				if (options || !this.retrieve('idle:options')) {
					this.set('idle', options);
				}
				this.store('idle', new IdleTimer(this, this.retrieve('idle:options')));
			}
			return this.retrieve('idle');
		}

	};

	Element.Events.idle = {
		onAdd: function (fn) {
			var global = this.get ? false : true;
			var idler = global ? window.idleTimer : this.get('idle');
			if (global && !idler) {
				idler = window.idleTimer = new IdleTimer(Browser.ie ? document : this);
			}
			if (!idler.started) { idler.start(); }
			idler.addEvent('idle', fn);
		}
	};
	Element.Events.active = {
		onAdd: function (fn) {
			(this.get ? this.get('idle') : window.idleTimer).addEvent('active', fn);
		}
	};

	return IdleTimer;
});